
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <cuda.h>
#include <time.h>
#include <cstdlib>

using namespace std;

cudaError_t addWithCuda(float *c, float *a, float *b, unsigned int row, unsigned int col);

__global__ void addKernel(float *c, float *a,  float *b,unsigned int bias, unsigned int row, unsigned int col)
{
	int i, cur_col,cur_row;
	i = threadIdx.x+blockIdx.x*blockDim.x + bias;
	cur_col = i % col;
	cur_row = i/row;
	if (i < row*col){
		//c[cur_col] = 1;
		c[cur_col] = c[cur_col] + a[cur_col + row*cur_row] * b[cur_col];
	}
	
}

float* randomizeVector(unsigned int len)
{
	float *res;
	float v = 0;
	res = (float *)malloc(len*sizeof(float));
	for (unsigned int j = 0; j<len; j++){
		v = 2*(float)rand()/RAND_MAX - 1;
		res[j] = v;
	}
	return res;
}

int main()
{
	cudaDeviceReset();
    unsigned int row, col;
    float *a, *b, *c;//a - matrix, b - vector, c - vector
	row = 10000;
	col = 10000;
	int seconds;

	time_t start,end;

	a = randomizeVector(row*col);
	b = randomizeVector(col);
	c = (float *)calloc(col, sizeof(float));
    // Add vectors in parallel.

	time(&start);
    cudaError_t cudaStatus = addWithCuda(c, a, b, row, col);
	time(&end);
	
	seconds = difftime(end,start);
	printf("calculation time is %d seconds\n", seconds);

    if (cudaStatus != cudaSuccess) {
		fprintf(stderr, "addWithCuda failed!");
		return 1;
    }

    printf("{1,2,3,4,5} + {10,20,30,40,50} = {%f,%f,%f,%f,%f}\n",
        c[0], c[1], c[2], c[3], c[4]);

    // cudaDeviceReset must be called before exiting in order for profiling and
    // tracing tools such as Nsight and Visual Profiler to show complete traces.

    cudaStatus = cudaDeviceReset();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceReset failed!");
        return 1;
    }
	cin.get();
    return 0;
	
}

// Helper function for using CUDA to add vectors in parallel.
cudaError_t addWithCuda(float *c, float *a, float *b, unsigned int row, unsigned int col)
{
    float *dev_a = 0;
    float *dev_b = 0;
    float *dev_c = 0;
    cudaError_t cudaStatus;
	int maxThread = 512;
	int maxBlock = 65535;
	unsigned int bias = 0;

    // Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(0);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
        goto Error;
    }

    // Allocate GPU buffers for three vectors (two input, one output)    .
    cudaStatus = cudaMalloc((void**)&dev_c, col * sizeof(float));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }
	cudaMemset(dev_c, 0, col * sizeof(float));

//	size = 784*1000+1000*1000+1000*500+500*30;
    cudaStatus = cudaMalloc((void**)&dev_a, col*row * sizeof(float));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_b, col * sizeof(float));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    // Copy input vectors from host memory to GPU buffers.
    cudaStatus = cudaMemcpy(dev_a, a, row*col * sizeof(float), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    cudaStatus = cudaMemcpy(dev_b, b, col * sizeof(float), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    // Launch a kernel on the GPU with one thread for each element.
	dim3 blocks(65535);
	dim3 threads(512);
	bias = 0;
	for (int k = 0; bias<row*col;k++){
		addKernel<<<blocks, threads>>>(dev_c, dev_a, dev_b, bias, row, col);
		bias += maxThread*maxBlock;

		    // Check for any errors launching the kernel
		cudaStatus = cudaGetLastError();
		if (cudaStatus != cudaSuccess) {
			fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
			goto Error;
		}
    
		// cudaDeviceSynchronize waits for the kernel to finish, and returns
		// any errors encountered during the launch.
		cudaStatus = cudaDeviceSynchronize();
		if (cudaStatus != cudaSuccess) {
			fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
			goto Error;
		}

	}



    // Copy output vector from GPU buffer to host memory.
    cudaStatus = cudaMemcpy(c, dev_c, col * sizeof(float), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

Error:
    cudaFree(dev_c);
    cudaFree(dev_a);
    cudaFree(dev_b);
    
    return cudaStatus;
}



